package p21_参数解析器;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.StrUtil;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.bind.support.DefaultDataBinderFactory;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.stream.Collectors;

/**
 * 这节介绍参数解析器
 * 参数解析器能干啥：
 * 看是否支持某个参数
 * 如果支持，获取参数的值
 *
 * @RequestParam，@CookieValue等注解的参数，能适应${},#{}表达式
 */
public class _1_ArgResolver {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
        HttpServletRequest request = MockRequest.mockRequest();

        //1.将控制器方法封装成一个HandlerMethod对象
        HandlerMethod handlerMethod = new HandlerMethod(new Controller(),
                Controller.class.getMethod("test", String.class, String.class, int.class,
                        String.class, MultipartFile.class, int.class, String.class, String.class, String.class,
                        HttpServletRequest.class, Person.class, Person.class, Person.class));

        //2.准备对象绑定与类型转换工厂。请求中的参数都是字符串(也只能是字符串)，如果控制器方法参数是非字符串类型，需要进行转换(转换成参数的真实类型)
        DefaultDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);

        //3.准备ModelAndViewContainer用来存储中间Model结果，模型数据(mvc的model，前后端分离几乎不用)
        ModelAndViewContainer container = new ModelAndViewContainer();

        //4.遍历HandlerMethod中的每一个参数，并挨个解析，寻找合适的参数解析器来为该参数赋值
        for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
            //解析参数名称的组件(不加这个组件，获取不到参数名称)
            parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
            //获取参数上的注解
            String anno = Arrays.stream(parameter.getParameterAnnotations()).map(i -> "@" + i.annotationType().getSimpleName()).collect(Collectors.joining(" "));
            //打印参数
            //System.out.println(anno + " " +parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName());
            String str;

            /**
             * 使用参数解析器解析参数，可以直接使用某个参数解析器
             * 可以使用组合器来组合多个参数，组合器会选择一个能够解析当前参数的解析器来进行解析，体现了组合模式
             * 下面定义的参数解析器，有先后限制
             */
            HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
            composite.addResolvers(
                    /**
                     * 解析带@RequestParam注解的参数
                     *    参数为:容器对象(解析出的数据可以来自容器),目标参数是否可以忽略@RequestParam(false表示只解析带了@RequestParam的参数)
                     * 最后面也有一个RequestParamMethodArgumentResolver，只不过这个是可以解析不带@RequestParam参数的
                     */
                    new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), false),
                    //路径映射解析器，@PathVariable(从请求域中获取map，以路径通配符作为key获取真实的请求路径，然后解析出数据赋值给参数)
                    new PathVariableMethodArgumentResolver(),
                    //请求头解析器，解析@RequestHeader
                    new RequestHeaderMethodArgumentResolver(context.getDefaultListableBeanFactory()),
                    //cookie解析器,解析@CookieValue
                    new ServletCookieValueMethodArgumentResolver(context.getDefaultListableBeanFactory()),
                    //从容器获取，解析${}->环境变量, #{}->el表达式解析
                    new ExpressionValueMethodArgumentResolver(context.getDefaultListableBeanFactory()),
                    //根据参数类型进行解析，解析HttpServletRequest等特殊对象
                    new ServletRequestMethodArgumentResolver(),
                    //解析@ModelAttribute模型数据，并将解析出的模型对象存入ModelAndViewContainer.参数false:必须有@ModelAttribute;true:可以省略@ModelAttribute
                    new ServletModelAttributeMethodProcessor(false),
                    /**
                     * 处理@RequestBody的参数解析器，参数传一个消息转换器数组.消息转换器后面介绍
                     * 必须放在new ServletModelAttributeMethodProcessor(true)前面，否则这个参数会被该解析器解析
                     */
                    new RequestResponseBodyMethodProcessor(ListUtil.of(new MappingJackson2HttpMessageConverter())),
                    //处理省略了@ModelAttribute的模型参数(也会将处理结果放入入ModelAndViewContainer)
                    new ServletModelAttributeMethodProcessor(true),
                    //作用同第一个参数解析器一样，只不过这个是解析省略了@RequestParam的参数(true，省略了@RequestParam的参数也能解析)，而且放在最后
                    new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), true)
            );
            /**
             * 设置参数解析器顺序时，带什么什么注解，或者特定什么什么类型的解析放前面，不带什么什么的放后面(优先级最低)
             */

            //组合器会选择一个合适的参数解析器来进行目标参数(实际就是挨个调用内部的参数解析器的supportsParameter方法)
            if (composite.supportsParameter(parameter)) {
                //如果能够解析，就从请求对象或容器解析出一个数据，进行返回.springmvc就将返回的数据赋值给目标控制器方法
                Object arg = composite.resolveArgument(parameter, container, new ServletWebRequest(request), factory);
                str = StrUtil.format("[{}] {} {} {} -> {}",
                        parameter.getParameterIndex(),
                        anno,
                        parameter.getParameterType().getSimpleName(),
                        parameter.getParameterName(),
                        arg);
            }
            //不能解析
            else {
                str = StrUtil.format("[{}] {} {} {}",
                        parameter.getParameterIndex(),
                        anno,
                        parameter.getParameterType().getSimpleName(),
                        parameter.getParameterName());
            }

            System.out.println(str);
            System.out.println("   ===>模型数据" + container.getModel());
        }
    }
}
